import BasePlay from './base/BasePlay';
import { menu } from '../../../app/decorator';

const { ccclass, property } = cc._decorator;

interface Movie {
    frameRate: number,
    frames: {
        x: number,
        y: number,
        res: string
    }[]
}

interface MovieJsonRes {
    [name: string]: {
        x: number,
        y: number,
        w: number,
        h: number
    }
}

interface MovieJsonRc {
    [name: string]: Movie
}

interface MovieJson {
    res: MovieJsonRes,
    mc: MovieJsonRc
}

interface JsonAsset extends cc.JsonAsset {
    json: MovieJson
}

function setEnumAttr(obj, propName, enumDef) {
    cc.Class['Attr'].setClassAttr(obj, propName, 'type', 'Enum');
    cc.Class['Attr'].setClassAttr(obj, propName, 'enumList', cc.Enum['getList'](enumDef));
}

const DefaultAnimsEnum = cc.Enum({ '<None>': 0 });

@ccclass
@menu(__filename)
export default class PlayEgretMovie extends BasePlay {
    /**
     * 锚点数据
     */
    @property(cc.Vec2)
    private _movieAnchor: cc.Vec2 = cc.v2(0, 1);
    @property(cc.Vec2)
    get movieAnchor(): cc.Vec2 { return this._movieAnchor; }
    set movieAnchor(value) {
        this._movieAnchor = value;
        if (this._sprite) {
            this._sprite.node.anchorX = this.movieAnchor.x;
            this._sprite.node.anchorY = this.movieAnchor.y;
        }
    }

    /**
     * 纹理数据
     */
    @property(cc.SpriteFrame)
    private _texture: cc.SpriteFrame = null;
    @property(cc.SpriteFrame)
    get texture(): cc.SpriteFrame { return this._texture; }
    set texture(value) {
        if (this._texture !== value) {
            this._clearFrameCache();
        }
        this._texture = value;
        if (CC_EDITOR) this.json = null;
    }

    /**
     * json数据
     */
    @property
    private _json: JsonAsset = null;
    @property(cc.JsonAsset)
    get json(): cc.JsonAsset { return this._json; }
    set json(value) {
        this._json = value;
        if (CC_EDITOR) this.__animation = '<None>';
        if (CC_EDITOR && !cc['engine'].isPlaying) {
            this._refreshInspector();
        }
    }

    /**
     * 动作名字
     */
    @property
    private __animation: string = '<None>';
    @property({
        visible: false
    })
    get animation(): string {
        return this.__animation;
    }
    set animation(value: string) {
        this.__animation = value;
        if (CC_EDITOR) {
            this._play(-1);
        } else {
            this._play();
        }
    }

    /**
     * 用于在编辑器面板上显示动作名字列表
     */
    @property({
        type: DefaultAnimsEnum,
        visible: true
    })
    private get _animation() {
        if (!CC_EDITOR) return this.__animation;
        const animEnum = this._getAnimsEnum() || DefaultAnimsEnum;
        const num = this.animation ? animEnum[this.animation] : 0;
        return typeof num === 'undefined' ? 0 : num;
    }
    private set _animation(value) {
        if (!CC_EDITOR) return;
        const animEnum = this._getAnimsEnum() || DefaultAnimsEnum;
        this.animation = animEnum[value];
    }

    static Event = {
        moviePlay: 'moviePlay',
        movieStop: 'movieStop',
        moviePause: 'moviePause',
        movieResume: 'movieResume',
        movieComplete: 'movieComplete'
    };

    //纹理
    private get _texture2d(): cc.Texture2D {
        return this._texture ? this._texture.getTexture() : null;
    }
    //存放图片信息
    private get _res(): MovieJsonRes {
        return this._json ? this._json.json.res : null;
    }
    //存放动作信息
    private get _mc(): MovieJsonRc {
        return this._json ? this._json.json.mc : null;
    }
    //当前动作信息
    private _currMovie: Movie = null;
    //当前动作执行中的下标
    private _currIndex = 0;
    //frame缓存
    private _frameCache: Map<string, cc.SpriteFrame> = new Map();
    //Sprite
    private _sprite: cc.Sprite = null;

    private _getAnimsEnum(): object {
        if (this.json && this.json.json.mc) {
            let obj = { '<None>': 0 };
            Object.keys(this.json.json.mc).forEach(function (animation, index) {
                obj[animation] = index + 1;
            });
            return cc.Enum(obj);
        } else {
            return null;
        }
    }
    protected _updateAnimEnum = CC_EDITOR && function () {
        const animEnum = this._getAnimsEnum() || DefaultAnimsEnum;
        // change enum
        setEnumAttr(this, '_animation', animEnum);
    };
    protected _refreshInspector = CC_EDITOR && function () {
        // update inspector
        this._updateAnimEnum();
        // @ts-ignore
        Editor.Utils.refreshSelectedInspector('node', this.node.uuid);
    };

    protected __preload = CC_EDITOR && function () {
        this._refreshInspector();
    };

    onLoad() {
        if (CC_EDITOR && this.node.getChildByName('#privateMovieClip')) {
            const privateNode = this.node.getChildByName('#privateMovieClip');
            this._sprite = privateNode.getComponent(cc.Sprite);
        } else {
            const privateNode = new cc.Node('#privateMovieClip');
            privateNode['_localZOrder'] = cc.macro.MIN_ZINDEX << 16;
            if (CC_EDITOR) {
                privateNode['_objFlags'] |= (cc.Object['Flags'].DontSave | cc.Object['Flags'].LockedInEditor | cc.Object['Flags'].HideInHierarchy);
            }
            this._sprite = privateNode.addComponent(cc.Sprite);
            privateNode.parent = this.node;
            privateNode.anchorX = this.movieAnchor.x;
            privateNode.anchorY = this.movieAnchor.y;
        }

        super.onLoad();

        // 编辑器模式下，默认显示第一帧
        if (CC_EDITOR) this.gotoFrame();
    }

    private _clearFrameCache() {
        if (this._sprite) this._sprite.spriteFrame = null;
        this._frameCache.forEach(this._deleteFrame);
        this._frameCache.clear();
    }

    private _createFrame(texture: cc.Texture2D, rect: cc.Rect): cc.SpriteFrame {
        const frame = new cc.SpriteFrame(texture, rect);
        frame.addRef();
        return frame;
    }

    private _deleteFrame(frame: cc.SpriteFrame) {
        frame.decRef();
        frame.destroy();
    }

    private _getFrameByName(name: string) {
        if (!this._frameCache.has(name) && this._res && this._res[name]) {
            this._frameCache.set(name, this._createFrame(this._texture2d, cc.rect(this._res[name].x, this._res[name].y, this._res[name].w, this._res[name].h)));
        }
        return this._frameCache.get(name) || null;
    }

    private _getMovie(name: string): Movie {
        if (this._mc) {
            return this._mc[name] || null;
        } else {
            return null;
        }
    }

    private _renderFrame(index) {
        if (this._sprite && this._currMovie) {
            const data = this._currMovie.frames[index];
            // 必须先设置null，不然在编辑器中预览会导致不刷新
            this._sprite.spriteFrame = null;
            this._sprite.spriteFrame = this._getFrameByName(data.res);
            this._sprite.node.x = data.x;
            this._sprite.node.y = -data.y;
        }
    }

    protected _render() {
        this._renderFrame(this._currIndex);

        if (++this._currIndex >= this._currMovie.frames.length) {
            if (!CC_EDITOR) this.node.emit(PlayEgretMovie.Event.movieComplete);
            this._currIndex = 0;
            return false;
        }
        return true;
    }

    protected _play(loopCount?: number): boolean {
        this.stop();

        this._currIndex = 0;
        this._currMovie = this._getMovie(this.animation);

        if (!this._currMovie) return false;

        super.basePlay(1 / this._currMovie.frameRate, loopCount);

        // 编辑器下先显示一帧
        if (CC_EDITOR) this._renderFrame(this._currIndex);
        return true;
    }

    gotoFrame(index = 0) {
        if (!this._currMovie) {
            this._currMovie = this._getMovie(this.animation);
            if (this._currMovie) {
                this._renderFrame(index % this._currMovie.frames.length);
            }
        } else {
            this._currIndex = index % this._currMovie.frames.length;
            if (this.isStopped() || this.isPausing()) {
                this._renderFrame(this._currIndex);
            }
        }
    }

    /**
     * 播放动画
     * @param name 动作名字
     * @param playTimes 播放次数(默认无限循环)
     */
    play(name?: string, playTimes = -1) {
        if (typeof name === 'string') {
            this.__animation = name;
        }
        playTimes = Math.floor(playTimes);
        this.stop();

        // 0代表不播放
        if (playTimes === 0) return false;
        // 播放失败
        if (!this._play(playTimes - 1)) return false;

        if (!CC_EDITOR) this.node.emit(PlayEgretMovie.Event.moviePlay);

        return true;
    }

    /**
     * 播放一次
     * @param name 动作名字
     */
    playOnce(name?: string) {
        return this.play(name, 1);
    }

    stop() {
        if (super.stop()) {
            this._currMovie = null;
            this._currIndex = 0;
            // this._sprite.spriteFrame = null;
            if (!CC_EDITOR) this.node.emit(PlayEgretMovie.Event.movieStop);
            return true;
        }
        return false;
    }

    pause() {
        if (super.pause()) {
            if (!CC_EDITOR) this.node.emit(PlayEgretMovie.Event.moviePause);
            return true;
        }
        return false;
    }

    resume() {
        if (super.resume()) {
            if (!CC_EDITOR) this.node.emit(PlayEgretMovie.Event.movieResume);
            return true;
        }
        return false;
    }
}
